﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.IO;

using NAnt.Core;
using NAnt.Core.Attributes;
using NAnt.Core.Tasks;
using NAnt.Core.Types;
using NAnt.DotNet.Types;
using NAnt.Babel.Types;

namespace NAnt.Babel.Tasks
{
	[TaskName("babel")]
	public class BabelTask : ExternalProgramBase
	{
        #region Fields
        private string _programArgs;
        private DirectoryInfo _babeldirectory;
        private RegexItemCollection _quickRules;
        private RegexItemCollection _satelliteAssemblies;
        private RegexItemCollection _searchDirectories;
        private RegexItemCollection _dynamicProxyCallFilters;
        private RegexItemCollection _instruments;
        private RegexItemCollection _takeFiles;
        private RegexItemCollection _skipFiles;
        private RegexItemCollection _noWarnings;
        private RegexItemCollection _warningsAsErrors;

        private Hashtable _bag;
        
        #endregion

        #region Properties
        /// <summary>
        /// Gets the collection of parameters used by the task class.
        /// </summary>
        /// <value>Parameter bag.</value>
        protected internal Hashtable Bag
        {
            get
            {
                return _bag;
            }
        }

        /// <summary>
        /// The Babel input file that is to be obfuscated 
        /// </summary>
        [TaskAttribute("babeldirectory")]
        public DirectoryInfo BabelDirectory
        {
            get { return _babeldirectory; }
            set { _babeldirectory = value; }
        }

        /// <summary>
        /// Babel obfuscator install directory name
        /// </summary>
        private string StandardBabelDirectory
        {
            get { return "Babel"; }
        }

        /// <summary>
        /// Babel Obfuscator executable name.
        /// </summary>
        public string ToolName
        {
            get { return "babel.exe"; }
        }

        #region Input Files
        /// <summary>
        /// Target assembly file.
        /// </summary>
        [TaskAttribute("inputfile", Required = true)]
        public FileInfo InputFile
        {
            get { return (FileInfo)_bag["inputfile"]; }
            set { _bag["inputfile"] = value; }
        }

        /// <summary>
        /// Babel project file name
        /// </summary>
        [TaskAttribute("project")]
        public FileInfo ProjectFile
        {
            get { return (FileInfo)_bag["project"]; }
            set { _bag["project"] = value; }
        }

        /// <summary>
        /// Strong name file path.
        /// </summary>
        [TaskAttribute("keyfile")]
        public FileInfo KeyFile
        {
            get { return (FileInfo)_bag["keyfile"]; }
            set { _bag["keyfile"] = value; }
        }

        /// <summary>
        /// Strong name file password.
        /// </summary>
        [TaskAttribute("keypwd")]
        public string KeyPwd
        {
            get { return (string)_bag["keypwd"]; }
            set { _bag["keypwd"] = value; }
        }

        /// <summary>
        /// Name of the container used to strong sign the obfuscated assembly.
        /// </summary>
        [TaskAttribute("keycontainer")]
        [StringValidator(AllowEmpty = false)]
        public string KeyContainer
        {
            get { return (string)_bag["keycontainer"]; }
            set { _bag["keycontainer"] = value; }
        }

        /// <summary>
        /// XML rules file paths.
        /// </summary>
        [BuildElement("rulesfiles")]
        public FileSet RulesFiles
        {
            get { return (FileSet)_bag["rules"]; }
            set { _bag["rules"] = value; }
        }

        /// <summary>
        /// Obfuscation input map files.
        /// </summary>
        [BuildElement("mapinfiles")]
        public FileSet MapInFiles
        {
            get { return (FileSet)_bag["mapin"]; }
            set { _bag["mapin"] = value; }
        }
        #endregion // Input Files

        #region Output Files
        /// <summary>
        /// The obuscated file path.
        /// </summary>
        [TaskAttribute("outputfile")]
        public FileInfo OutputFile
        {
            get { return (FileInfo)_bag["outputfile"]; }
            set { _bag["outputfile"] = value; }
        }

        /// <summary>
        /// The file path to generated PDB debug symbols.
        /// </summary>
        [TaskAttribute("pdbfile")]
        public FileInfo PdbFile
        {
            get { return (FileInfo)_bag["pdbfile"]; }
            set { _bag["pdbfile"] = value; }
        }

        /// <summary>
        /// Whether to enable the generation of the obfuscation log.
        /// </summary>
        [TaskAttribute("generatelogfile")]
        public bool GenerateLogFile
        {
            get { return (bool)_bag["generatelogfile"]; }
            set { _bag["generatelogfile"] = value; }
        }

        /// <summary>
        /// The log file path.
        /// </summary>
        [TaskAttribute("logfile")]
        public FileInfo LogFile
        {
            get { return (FileInfo)_bag["logfile"]; }
            set { _bag["logfile"] = value; }
        }

        /// <summary>
        /// Whether to enable the generation of the mapping file.
        /// </summary>
        [TaskAttribute("generatemapoutfile")]
        public bool GenerateMapOutFile
        {
            get { return (bool)_bag["generatemapoutfile"]; }
            set { _bag["generatemapoutfile"] = value; }
        }

        /// <summary>
        /// The mapping file path.
        /// </summary>
        [TaskAttribute("mapoutfile")]
        public FileInfo MapOutFile
        {
            get { return (FileInfo)_bag["mapout"]; }
            set { _bag["mapout"] = value; }
        }

        /// <summary>
        /// Path where to save the obfuscation project file.
        /// </summary>
        [TaskAttribute("makeproject")]
        public FileInfo MakeProject
        {
            get { return (FileInfo)_bag["makeproject"]; }
            set { _bag["makeproject"] = value; }
        }

        #endregion // Output Files

        #region Miscellaneous
        /// <summary>
        /// Whether to show the logo.
        /// </summary>
        [TaskAttribute("showlogo")]
        public bool ShowLogo
        {
            get { return (bool)_bag["logo"]; }
            set { _bag["logo"] = value; }
        }

        /// <summary>
        /// Set the output verbose level
        /// </summary>
        [TaskAttribute("verboselevel")]
        public int VerboseLevel
        {
            get { return (int)_bag["verbose"]; }
            set { _bag["verbose"] = value; }
        }

        /// <summary>
        /// Whether to enable the obfuscation agent.
        /// </summary>
        [TaskAttribute("enableobfuscationagent")]
        public bool EnableObfuscationAgent
        {
            get { return (bool)_bag["agent"]; }
            set { _bag["agent"] = value; }
        }

        /// <summary>
        /// Whether to search for satellite assemblies.
        /// </summary>
        [TaskAttribute("searchsatelliteassemblies")]
        public bool SearchSatelliteAssemblies
        {
            get { return (bool)_bag["searchsatelliteassemblies"]; }
            set { _bag["searchsatelliteassemblies"] = value; }
        }

        /// <summary>
        /// The collection of satellite assemblies full path.
        /// </summary>
        [BuildElementCollection("satelliteassemblies", "satelliteassembly")]
        public RegexItemCollection SatelliteAssemblies
        {
            get { return _satelliteAssemblies; }
        }

        /// <summary>
        /// The collection of external assembly search directories.
        /// </summary>
        [BuildElementCollection("searchdirectories", "searchdirectory")]
        public RegexItemCollection SearchDirectories
        {
            get { return _searchDirectories; }
        }

        /// <summary>
        /// The collection of regex filter used to take assembly to obfuscate inside a package.
        /// </summary>
        [BuildElementCollection("takefiles", "takefile")]
        public RegexItemCollection TakeFiles
        {
            get { return _takeFiles; }
        }

        /// <summary>
        /// The collection of regex filter used to skip assembly to obfuscate inside a package.
        /// </summary>
        [BuildElementCollection("skipfiles", "skipfile")]
        public RegexItemCollection SkipFiles
        {
            get { return _skipFiles; }
        }

        /// <summary>
        /// Set the output XAP package compression level.
        /// </summary>
        [TaskAttribute("xapcompressionlevel")]
        public int XapCompressionLevel
        {
            get { return (int)_bag["compress"]; }
            set { _bag["compress"] = value; }
        }

        /// <summary>
        /// Whether to show obfuscation statistics.
        /// </summary>
        [TaskAttribute("showstatistics")]
        public bool ShowStatistics
        {
            get { return (bool)_bag["stat"]; }
            set { _bag["stat"] = value; }
        }

        /// <summary>
        /// Whether to disable the load of application configuration.
        /// </summary
        [TaskAttribute("noconfiguration")]
        public bool NoConfiguration
        {
            get { return (bool)_bag["NoConfiguration"]; }
            set { _bag["NoConfiguration"] = value; }
        }

        /// <summary>
        /// The list to warining codes to disable.
        /// </summary
        [BuildElementCollection("nowarnings", "nowarning")]
        public RegexItemCollection NoWarnings
        {
            get { return _noWarnings; }
        }

        /// <summary>
        /// The list to warining codes to consider as errors.
        /// </summary
        [BuildElementCollection("warningsaserrors", "warning")]
        public RegexItemCollection WarningsAsErrors
        {
            get { return _warningsAsErrors; }
        }

        /// <summary>
        /// A set of quick obfuscation rules.
        /// </summary
        [BuildElementCollection("quickrules", "quickrule")]
        public RegexItemCollection QuickRules
        {
            get { return _quickRules; }
        }

        /// <summary>
        /// Whether to remove the strong name from the obfuscated assembly.
        /// </summary
        [TaskAttribute("removekey")]
        public bool RemoveKey
        {
            get { return (bool)_bag["removekey"]; }
            set { _bag["removekey"] = value; }
        }

        #endregion // Miscellaneous

        #region Merge and Embed Assemblies
        /// <summary>
        /// Set of assemblies to embed into the target assembly.
        /// </summary
        [BuildElement("embed")]
        public AssemblyFileSet EmbedAssemblies
        {
            get { return (AssemblyFileSet)_bag["embed"]; }
            set { _bag["embed"] = value; }
        }

        /// <summary>
        /// Set of assemblies to merge into the target assembly.
        /// </summary
        [BuildElement("merge")]
        public AssemblyFileSet MergeAssemblies
        {
            get { return (AssemblyFileSet)_bag["merge"]; }
            set { _bag["merge"] = value; }
        }

        /// <summary>
        /// Whether to internalize the merged types.
        /// </summary
        [TaskAttribute("internalize")]
        public bool Internalize
        {
            get { return (bool)_bag["internalize"]; }
            set { _bag["internalize"] = value; }
        }

        /// <summary>
        /// Whether to copy assembly level attributes of merged assemblies.
        /// </summary
        [TaskAttribute("copyattributes")]
        public bool CopyAttributes
        {
            get { return (bool)_bag["copyattributes"]; }
            set { _bag["copyattributes"] = value; }
        }
        #endregion // Merge and Embed Assemblies

        #region Renaming
        /// <summary>
        /// Whether to rename types.
        /// </summary
        [TaskAttribute("obfuscatetypes")]
        public bool ObfuscateTypes
        {
            get { return (bool)_bag["types"]; }
            set { _bag["types"] = value; }
        }
        /// <summary>
        /// Whether to rename events.
        /// </summary
        [TaskAttribute("obfuscateevents")]
        public bool ObfuscateEvents
        {
            get { return (bool)_bag["events"]; }
            set { _bag["events"] = value; }
        }
        /// <summary>
        /// Whether to rename methods.
        /// </summary
        [TaskAttribute("obfuscatemethods")]
        public bool ObfuscateMethods
        {
            get { return (bool)_bag["methods"]; }
            set { _bag["methods"] = value; }
        }
        /// <summary>
        /// Whether to rename properties.
        /// </summary
        [TaskAttribute("obfuscateproperties")]
        public bool ObfuscateProperties
        {
            get { return (bool)_bag["properties"]; }
            set { _bag["properties"] = value; }
        }
        /// <summary>
        /// Whether to rename fields.
        /// </summary
        [TaskAttribute("obfuscatefields")]
        public bool ObfuscateFields
        {
            get { return (bool)_bag["fields"]; }
            set { _bag["fields"] = value; }
        }

        /// <summary>
        /// Whether to rename XAML or BAML symbols.
        /// </summary
        [BuildElementArray("obfuscatexaml")]
        public RegexItem[] ObfuscateXaml
        {
            get { return (RegexItem[])_bag["xaml"]; }
            set { _bag["xaml"] = value; }
        }

        /// <summary>
        /// Whether to remove namespace information from type names.
        /// </summary
        [TaskAttribute("flattennamespaces")]
        public string FlattenNamespaces
        {
            get { return (string)_bag["flatns"]; }
            set { _bag["flatns"] = value; }
        }

        /// <summary>
        /// Whether to use unprintable characters for obfuscated names.
        /// </summary
        [TaskAttribute("unicodenormalization")]
        public string UnicodeNormalization
        {
            get { return (string)_bag["unicode"]; }
            set { _bag["unicode"] = value; }
        }

        /// <summary>
        /// Whether to rename virtual symbols.
        /// </summary
        [TaskAttribute("virtualfunctions")]
        public bool VirtualFunctions
        {
            get { return (bool)_bag["virtual"]; }
            set { _bag["virtual"] = value; }
        }

        /// <summary>
        /// Whether to use enhanced overloading.
        /// </summary
        [TaskAttribute("overloadedrenaming")]
        public bool OverloadedRenaming
        {
            get { return (bool)_bag["overloaded"]; }
            set { _bag["overloaded"] = value; }
        }

        /// <summary>
        /// Set the minimum length for obfuscated names.
        /// </summary>
        [TaskAttribute("namelength")]
        public int NameLength
        {
            get { return (int)_bag["namelength"]; }
            set { _bag["namelength"] = value; }
        }
        #endregion // Renaming

        #region Control Flow Obfuscation
        /// <summary>
        /// Whether to enable control flow obfuscation
        /// </summary>
        [BuildElementArray("controlflow")]
        public RegexItem[] ControlFlow
        {
            get { return (RegexItem[])_bag["controlflow"]; }
            set { _bag["controlflow"] = value; }
        }

        /// <summary>
        /// Whether to enable the emission of invalid instructions.
        /// </summary>
        [TaskAttribute("emitinvalidopcodes")]
        public string EmitInvalidOpcodes
        {
            get { return (string)_bag["invalidopcodes"]; }
            set { _bag["invalidopcodes"] = value; }
        }

        /// <summary>
        /// Set the number of iterations to use in control flow obfuscation.
        /// </summary>
        [TaskAttribute("iliterations")]
        public int ILIterations
        {
            get { return (int)_bag["iliterations"]; }
            set { _bag["iliterations"] = value; }
        }
        #endregion // Control Flow Obfuscation

        #region Encryption and Protection
        /// <summary>
        /// Enables the code encryption using a regular expression to filter the encrypted methods.
        /// </summary>
        [BuildElementArray("msilencryption")]
        public RegexItem[] MsilEncryption
        {
            get { return (RegexItem[])_bag["msilencryption"]; }
            set { _bag["msilencryption"] = value; }
        }

        /// <summary>
        /// Enables string encryption.
        /// </summary>
        [TaskAttribute("stringencryption")]
        public string StringEncryption
        {
            get { return (string)_bag["stringencryption"]; }
            set { _bag["stringencryption"] = value; }
        }

        /// <summary>
        /// Enables constant values and array encryption.
        /// </summary>
        [BuildElementArray("valueencryption")]
        public RegexItem[] ValueEncryption
        {
            get { return (RegexItem[])_bag["valueencryption"]; }
            set { _bag["valueencryption"] = value; }
        }

        /// <summary>
        /// Whether to prevent ildasm.exe from disassembly the obfuscated assembly.
        /// </summary>
        [TaskAttribute("suppressildasm")]
        public bool SuppressIldasm
        {
            get { return (bool)_bag["suppressildasm"]; }
            set { _bag["suppressildasm"] = value; }
        }

        /// <summary>
        /// Whether to prevent inspection through reflection.
        /// </summary>
        [TaskAttribute("suppressreflection")]
        public bool SuppressReflection
        {
            get { return (bool)_bag["reflection"]; }
            set { _bag["reflection"] = value; }
        }

        /// <summary>
        /// Whether to encrypt embedded resources.
        /// </summary>
        [TaskAttribute("resourceencryption")]
        public bool ResourceEncryption
        {
            get { return (bool)_bag["resourceencryption"]; }
            set { _bag["resourceencryption"] = value; }
        }

        /// <summary>
        /// Whether to hide the calls to external and internal methods through the generation of dynamic proxies.
        /// </summary>
        [BuildElementArray("dynamicproxy")]
        public RegexItem[] DynamicProxy
        {
            get { return (RegexItem[])_bag["proxy"]; }
            set { _bag["proxy"] = value; }
        }

        /// <summary>
        /// Collection of regex filter used in dynamic proxy generation.
        /// </summary>
        [BuildElementCollection("dynamicproxyfilters", "dynamicproxyfilter")]
        public RegexItemCollection DynamicProxyCallFilters
        {
            get { return _dynamicProxyCallFilters; }
        }
        #endregion // Encryption and Protection

        #region Code Generation
        /// <summary>
        /// Set of assemblies to add to the collection of external references.
        /// </summary>
        [BuildElement("addreference")]
        public FileSet AddReferences
        {
            get { return (FileSet)_bag["addreference"]; }
            set { _bag["addreference"] = value; }
        }

        /// <summary>
        /// Enables the code instrumentation.
        /// </summary>
        [BuildElementCollection("instruments", "instrument")]
        public RegexItemCollection Instruments
        {
            get { return _instruments; }
        }

        /// <summary>
        /// Whether to enable code instrumentation for empty methods.
        /// </summary>
        [TaskAttribute("instrumentemptymethods")]
        public bool InstrumentEmptyMethods
        {
            get { return (bool)_bag["emptymethods"]; }
            set { _bag["emptymethods"] = value; }
        }

        /// <summary>
        /// Whether to enable removal of unused code.
        /// </summary>
        [TaskAttribute("deadcodeelimination")]
        public bool DeadCodeElimination
        {
            get { return (bool)_bag["deadcode"]; }
            set { _bag["deadcode"] = value; }
        }

        /// <summary>
        /// Whether to seal types.
        /// </summary>
        [TaskAttribute("sealclasses")]
        public bool SealClasses
        {
            get { return (bool)_bag["seal"]; }
            set { _bag["seal"] = value; }
        }

        /// <summary>
        /// Whether to cleanup unwanted attributes.
        /// </summary>
        [TaskAttribute("cleanattributes")]
        public bool CleanAttributes
        {
            get { return (bool)_bag["cleanattributes"]; }
            set { _bag["cleanattributes"] = value; }
        }

        /// <summary>
        /// Whether to remove System.Enum types.
        /// </summary>
        [TaskAttribute("enumremoval")]
        public bool EnumRemoval
        {
            get { return (bool)_bag["enumremoval"]; }
            set { _bag["enumremoval"] = value; }
        }

        /// <summary>
        /// Whether to remove constant fields.
        /// </summary>
        [TaskAttribute("constremoval")]
        public bool ConstRemoval
        {
            get { return (bool)_bag["constremoval"]; }
            set { _bag["constremoval"] = value; }
        }

        /// <summary>
        /// Whether to update PDB debug symbols.
        /// </summary>
        [TaskAttribute("generatedebug")]
        public bool GenerateDebug
        {
            get { return (bool)_bag["debug"]; }
            set { _bag["debug"] = value; }
        }
        #endregion // Code Generation

        #endregion // Properties

        #region Overrides
        public override string ProgramArguments
        {
            get
            {
                return _programArgs;
            }
        }

        public override string ExeName
        {
            get
            {
                return GenerateFullPathToTool();
            }
            set
            {
                base.ExeName = value;
            }
        }

        protected override void ExecuteTask()
        {
            GenerateCommandLineCommands();
            base.ExecuteTask();
        }
        #endregion // Overrides

        #region Constructors
        public BabelTask()
        {
            _bag = new Hashtable();
            _quickRules = new RegexItemCollection(); 
            _satelliteAssemblies = new RegexItemCollection();
            _searchDirectories = new RegexItemCollection();
            _dynamicProxyCallFilters = new RegexItemCollection();
            _instruments = new RegexItemCollection();
            _takeFiles = new RegexItemCollection();
            _skipFiles = new RegexItemCollection();
            _noWarnings = new RegexItemCollection();
            _warningsAsErrors = new RegexItemCollection();
        }
        
        #endregion

        #region Helpers
        /// <summary>
        /// Makes the babel.exe command line arguments.
        /// </summary>
        private void GenerateCommandLineCommands()
        {
            if (!this.InputFile.Exists)
                throw new BuildException(String.Format("Input file '{0}' does not exists.", this.InputFile.FullName));

            this.Log(Level.Info, "Babel Input File {0}", this.InputFile.FullName);

            StringWriter writer = new StringWriter();

            // Target assembly
            AppendSwitchFile(writer, null, this.InputFile);

            // Merge Assemblies
            AppendSwitchFileSet(writer, null, this.MergeAssemblies);

            if (HasProperty("noconfiguration"))
                writer.Write(" @");

            // Misc Options
            AppendSwitchBoolean(writer, "logo");
            AppendSwitch(writer, "verbose");
            AppendSwitchRegexCollection(writer, "nowarn", this.NoWarnings);
            AppendSwitchRegexCollection(writer, "warnaserror", this.WarningsAsErrors);
            AppendSwitchBoolean(writer, "stat");
            AppendSwitchBoolean(writer, "agent");
            AppendSwitchRegexCollection(writer, "satellite", this.SatelliteAssemblies);
            AppendSwitchRegexCollection(writer, "addsearch", this.SearchDirectories);
            AppendSwitchRegexCollection(writer, "take", this.TakeFiles);
            AppendSwitchRegexCollection(writer, "skip", this.SkipFiles); 
            AppendSwitch(writer, "compress");
            AppendSwitchRegexCollection(writer, "quickrule", this.QuickRules);
            AppendSwitchBoolean(writer, "removekey");

            // Input Files 
            AppendSwitchFile(writer, "keyfile", this.KeyFile);
            AppendSwitch(writer, "keyname");
            AppendSwitch(writer, "keypwd"); 
            AppendSwitchFileSet(writer, "rules", this.RulesFiles);
            AppendSwitchFileSet(writer, "mapin", this.MapInFiles);
            AppendSwitchFile(writer, "project", this.ProjectFile);

            // Merge & Embed
            AppendSwitchFileSet(writer, "embed", this.EmbedAssemblies);
            AppendSwitchBoolean(writer, "internalize");
            AppendSwitchBoolean(writer, "copyattrs");

            // Renaming
            AppendSwitchBoolean(writer, "types");
            AppendSwitchBoolean(writer, "events");
            AppendSwitchBoolean(writer, "methods");
            AppendSwitchBoolean(writer, "properties");
            AppendSwitchBoolean(writer, "fields");
            AppendSwitchRegexCollection(writer, "xaml", this.ObfuscateXaml);
            AppendSwitchBoolean(writer, "virtual");
            AppendSwitchBoolean(writer, "overloaded");
            AppendSwitchBoolean(writer, "flatns");
            AppendSwitchBoolean(writer, "unicode");
            AppendSwitch(writer, "namelength");

            // Control Flow Obfuscation
            AppendSwitchRegexCollection(writer, "controlflow", this.ControlFlow);
            AppendSwitch(writer, "iliterations");
            AppendSwitchBoolean(writer, "invalidopcodes");

            // Encryption and Protection
            AppendSwitchRegexCollection(writer, "msilencryption", this.MsilEncryption);
            AppendSwitchBoolean(writer, "stringencryption");
            AppendSwitchRegexCollection(writer, "valueencryption", this.ValueEncryption);
            AppendSwitchBoolean(writer, "resourceencryption");
            AppendSwitchBoolean(writer, "ildasm");
            AppendSwitchBoolean(writer, "reflection");
            AppendSwitchRegexCollection(writer, "proxy", this.DynamicProxy);
            AppendSwitchRegexCollection(writer, "dynamicproxycallfilters", this.DynamicProxyCallFilters);

            // Code Generation
            AppendSwitchFileSet(writer, "addreference", this.AddReferences);
            AppendSwitchRegexCollection(writer, "instrument", this.Instruments);
            AppendSwitchBoolean(writer, "emptymethods");
            AppendSwitchBoolean(writer, "deadcode");
            AppendSwitchBoolean(writer, "seal");
            AppendSwitchBoolean(writer, "enumremoval");
            AppendSwitchBoolean(writer, "constremoval");
            AppendSwitchBoolean(writer, "debug");

            // Output Files
            AppendSwitchFile(writer, "pdb", this.PdbFile);
            AppendSwitchFileOrBoolean(writer, this.LogFile, "generatelogfile", "logfile");
            AppendSwitchFileOrBoolean(writer, this.MapOutFile, "generatemapoutfile", "mapout");
            AppendSwitchFile(writer, "makeproject", this.MakeProject);
            AppendSwitchFile(writer, "out", this.OutputFile);

            _programArgs = writer.ToString();
        }

        /// <summary>
        /// Check the presence of a task property.
        /// </summary>
        /// <param name="property">The property name.</param>
        /// <returns>True if the task was set.</returns>
        private bool HasProperty(string property)
        {
            return _bag.ContainsKey(property);
        }

        /// <summary>
        /// Get the babel.exe full path.
        /// </summary>
        /// <returns>Full path to babel.exe executable.</returns>
        private string GenerateFullPathToTool()
        {
            DirectoryInfo babelDir = this.BabelDirectory;
            string path = null;
            if (babelDir != null)
                path = babelDir.FullName;

            if ((path == null) || (path.Length == 0))
            {
                string programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
                path = Path.Combine(programFiles, StandardBabelDirectory);
                if (!Directory.Exists(path))
                {
                    programFiles = Environment.GetEnvironmentVariable("ProgramW6432");
                    if (programFiles.Length == 0)
                        path = Path.Combine(programFiles, StandardBabelDirectory);
                }
            }
            return Path.Combine(path, ToolName);
        }

        /// <summary>
        /// Add switch command like: --name [value]
        /// </summary>
        /// <param name="writer">Command line writer.</param>
        /// <param name="name"></param>
        /// <param name="option"></param>
        private void AppendSwitch(StringWriter writer, string name, string option = null)
        {
            if (!HasProperty(name))
                return;

            writer.Write(string.Format(" --{0} {1}", name, Bag[name]));
        }

        /// <summary>
        /// Add switch command like: --[no]name [value]
        /// </summary>
        /// <param name="writer">Command line writer.</param>
        /// <param name="name"></param>
        /// <param name="option"></param>
        private void AppendSwitchBoolean(StringWriter writer, string name)
        {
            if (!HasProperty(name))
                return;

            var value = Bag[name];
            if (value == null)
                return;

            bool enabled = false;
            if ((value is bool) || ((value is string) && bool.TryParse((string)value, out enabled)))
            {
                if (value is bool)
                    enabled = (bool)value;

                writer.Write(string.Format(" --{0}{1}", enabled ? String.Empty : "no", name));
                return;
            }

            writer.Write(string.Format(" --{0} {1}", name, value));
        }

        /// <summary>
        /// Add multiple switch commmand like: --[no]name [value]
        /// </summary>
        /// <param name="writer">Command line writer.</param>
        /// <param name="name"></param>
        /// <param name="collection"></param>
        private void AppendSwitchRegexCollection(StringWriter writer, string name, IEnumerable collection)
        {
            if (collection == null)
                return;

            foreach (RegexItem item in collection)
	        {
                string value = item.Value;

                bool enabled;
                if (bool.TryParse(value, out enabled))
                {
                    writer.Write(string.Format(" --{0}{1}", enabled ? String.Empty : "no", name));
                }
                else
                {
                    writer.Write(string.Format(" --{0} {1}", name, value));
                }
	        }
        }

        private void AppendSwitchFileSet(StringWriter writer, string name, FileSet fileSet)
        {
            if (fileSet == null)
                return;

            if (fileSet.FileNames != null)
            {

                foreach (var fileName in fileSet.FileNames)
                {
                    if (string.IsNullOrEmpty(name))
                        writer.Write(string.Format(" \"{0}\"", fileName));
                    else
                        writer.Write(string.Format(" --{0} \"{1}\"", name, fileName));
                }
            }
        }

        private void AppendSwitchFile(StringWriter writer, string name, FileInfo value)
        {
            if (value != null)
            {
                if (string.IsNullOrEmpty(name))
                    writer.Write(string.Format(" \"{1}\"", name, value.FullName));
                else
                    writer.Write(string.Format(" --{0} \"{1}\"", name, value.FullName));
            }
        } 

        private void AppendSwitchFileOrBoolean(StringWriter writer, FileInfo file, string name, string option)
        {
            if (HasProperty(name))
            {
                bool value = (bool)_bag[name];
                if (value)
                {
                    if (file != null)
                        AppendSwitchFile(writer, option, file);
                    else
                        writer.Write(string.Format(" --{0}", option));
                }
            }
            else if (file != null)
                AppendSwitchFile(writer, option, file);
        }

        #endregion // Helpers
	}
}
